package main

import (
	"encoding/json"
	"fmt"
	"net"

	"gopkg.in/ini.v1"
)

type ChatInfo struct {
	InfoType   int    `json:"infotype"` //1.注册，2.登录，3.聊天消息
	UserName   string `json:"username"`
	UserPasswd string `json:"userpasswd"`
	Info       string `json:"info"`
}

//申明usermap
/************
1.usermap的key值数据类型为string值为username
2.usermap值数据类型为net.Conn值为每一个客户端的地址信息Conn
************/
var Usermap map[string]net.Conn

func main() {
	Usermap = make(map[string]net.Conn)
	listen, err := net.Listen("tcp", "192.168.1.107:8888")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer listen.Close()
	for {
		Conn, err := listen.Accept()
		if err != nil {
			fmt.Println(err)
			return
		}
		go DoSomething(Conn)
	}
}

// 为每一个连接分配一个协程做对应的事情，实现并发
func DoSomething(Conn net.Conn) {
	defer Conn.Close()
	buf := make([]byte, 1024)
	info := ChatInfo{}
	for {
		length, err := Conn.Read(buf)
		if err != nil {
			for key, value := range Usermap {
				if value == Conn {
					delete(Usermap, key) //如果连接断开，删除usermap中对应的值，
					fmt.Printf("[%s]断开了连接...\n", key)
				}
			}
			return
		}
		err = json.Unmarshal(buf[:length], &info)
		if err != nil {
			fmt.Println("DoSomething_json.unmarshal:", err)
			return
		}
		switch info.InfoType {
		case 1: //注册
			Register(info, Conn)
		case 2: //登录
			Login(info, Conn)
		case 3: //聊天消息转发
			Broadcast(info)
		}
	}
}

//注册功能函数
/*****************
1.不支持同名用户名注册
2.注册信息将保存到服务器userinfo.ini文件中(简单搞一下就不高数据库了)
3.如果服务器配置文件打开失败会导致注册失败
4.如果注册失败，会返回相应的失败信息
*****************/
func Register(info ChatInfo, Conn net.Conn) {
	var chatinfo ChatInfo
	cfg, err := ini.Load("./userinfo.ini")
	if err != nil {
		fmt.Println("打开配置文件失败！无法进入系统")
		chatinfo.Info = "服务器配置文件打开失败，注册失败！"
	}
	if cfg.Section("").HasKey(info.UserName) {
		chatinfo.Info = "用户名已存在，注册失败！"
	} else {
		cfg.Section("").Key(info.UserName).SetValue(info.UserPasswd)
		cfg.SaveTo("./userinfo.ini")
	}
	chatinfo.InfoType = 1 //注册
	data := make([]byte, 1024)
	data, err = json.Marshal(chatinfo)
	if err != nil {
		fmt.Println("register_json.marshal:", err)
		return
	}
	fmt.Printf("新用户注册：用户名：%s,密码：%s\n", info.UserName, info.UserPasswd)
	Conn.Write(data)
}

//登录功能函数
/******************
1.登录功的时候服务器会到配置文件做对应的账号密码比对
2.如果登录成功，将会把用户的用户名和net.Conn结构体保存到usermap中
便于在转发消息的时候从usermap中提取所有的net.Conn进行数据转发
******************/
func Login(info ChatInfo, Conn net.Conn) {
	var chatinfo ChatInfo
	cfg, err := ini.Load("./userinfo.ini")
	if err != nil {
		fmt.Println("配置文件导入失败！")
		chatinfo.Info = "服务器配置错误，登录失败！"
	}
	if cfg.Section("").HasKey(info.UserName) {
		if cfg.Section("").Key(info.UserName).Value() == info.UserPasswd {
			chatinfo.Info = "登录成功！"
			Usermap[info.UserName] = Conn
		} else {
			chatinfo.Info = "密码错误，登录失败！"
		}
	} else {
		chatinfo.Info = "用户不存在，登录失败！"
	}
	chatinfo.InfoType = 2
	data := make([]byte, 1024)
	data, err = json.Marshal(chatinfo)
	if err != nil {
		fmt.Println("Login_json.marshal:", err)
		return
	}
	fmt.Printf("用户登录：用户名：%s,密码：%s\n", info.UserName, info.UserPasswd)
	Conn.Write(data)
}

//聊天消息转发函数
/*********************
1.遍历usermap提取每个net.Conn做对应的消息转发
**********************/
func Broadcast(info ChatInfo) {
	fmt.Printf("[%s]说:%s\n", info.UserName, info.Info)
	data := make([]byte, 1024)
	data, err := json.Marshal(info)
	if err != nil {
		fmt.Println("Broadcast_json.marshal:", err)
		return
	}
	for _, Conn := range Usermap {
		_, err := Conn.Write(data)
		if err != nil {
			fmt.Println("Broadcast_Write:", err)
			return
		}
	}
}
